package project.newCode;
import java.util.ArrayList;

import project.hulpPakket.Color3f;
import project.hulpPakket.Point3f;
import project.hulpPakket.Vector3f;
import project.newCode.lights.Light;
import project.newCode.materials.Material;
import project.newCode.surfaces.Hittable;

public class Shader {
	private ArrayList<Light> lights;
	private Color3f background = new Color3f(0F,0F,0F);
	private static float epsilon = 0.001F;
	private float ambientIntensity = 0.1F;
	private boolean relighter;
	public Shader() {
		lights = new ArrayList<Light>();
	}
	public Shader(ArrayList<Light> newlights, Color3f background) {
		lights = new ArrayList<Light>();
		this.setLights(newlights);
		this.background = background;
	}
	public Shader(ArrayList<Light> newlights, float ambientIntensity) {
		lights = new ArrayList<Light>();
		this.setLights(newlights);
		this.setAmbientIntensity(ambientIntensity);
	}

	public Shader(Light light, float f) {
		lights = new ArrayList<Light>();
		lights.add(light);
		this.setAmbientIntensity(f);
	}
	public Color3f raycolor(Ray r, float t0, float t1, Hittable s, int recursion, float coefficient, Hitrecord h3) {
		Hitrecord h2 = new Hitrecord();
		Hitrecord h1 = new Hitrecord(h3.hitCounter);
		boolean hit = s.hit(r, t0, t1, h1);
		h3.hitCounter = h1.hitCounter;
		if(hit) {
			Material m = h1.material;
			// Ambient Color
			Color3f c = Color3f.Multiply(m.getCr(h1.tex),this.getAmbientIntensity());
			for(int i = 0; i < this.getLights().size(); i ++) {
				Light light = this.getLights().get(i);
				Vector3f l = light.getDirection(h1.intersection);
				float distance = (float) Math.sqrt(Vector3f.Inp(l, l));
				l = l.normalise();
				Ray shadow = new Ray(h1.intersection,l);
				if(!s.hit(shadow, epsilon,distance,h2)) {
					Vector3f h = Vector3f.Plus(l.normalise(), r.getDirection().multiply(-1F).normalise());
					h = h.normalise();
					// diffuse shading
					Color3f lcolor = light.getWeightedColor();
					if(relighter) {
						lcolor=new Color3f(1,1,1);
					}
					c = Color3f.Plus(c, Color3f.Multiply(m.getCr(h1.tex), Color3f.Multiply(lcolor,Math.max(0,Vector3f.Inp(h1.normal, l)))));
					// specular shading
					c = Color3f.Plus(c, Color3f.Multiply(m.getCs(), Color3f.Multiply(lcolor,(float) Math.pow(Vector3f.Inp(h1.normal, h),m.getPhongExp()))));
				}
			}
			if(coefficient > 0.001 && recursion<100 && (m.getNin()>0)) {
				float nin = h1.material.getNin();
				float nout = h1.material.getNout();
				float cosine = 0;
				Color3f k = new Color3f(0,0,0);
				Vector3f t = new Vector3f(0,0,0);
				Vector3f d = Point3f.Min(h1.intersection, r.getOrigin()).normalise();
				Vector3f n = h1.normal.clone().normalise();
				Vector3f refr = Vector3f.Min(d, n.multiply(2*Vector3f.Inp(d, n))).normalise();
				if(nout!=0) {
					if(Vector3f.Inp(d, n)<0) {
						// Ray hits from the outside
						this.refract(d,n,nin,nout,t);
						cosine = Vector3f.Inp(d.multiply(-1), n);
						k = new Color3f(1,1,1);
					}
					else {
						k = new Color3f((float) Math.exp(-1*h1.material.getCrefr().x*h1.t),(float) Math.exp(-1*h1.material.getCrefr().y*h1.t),(float) Math.exp(-1*h1.material.getCrefr().z*h1.t));
						if(this.refract(d, n.multiply(-1), 1/nin, 1/nout, t)) {
							cosine = Vector3f.Inp(t.normalise(), n);
							//Ray leaves
						}
						else {
							return Color3f.Multiply(k, this.raycolor(new Ray(h1.intersection,refr), epsilon, Float.MAX_VALUE, s, recursion+1,coefficient,h1));
							//Total internal reflection
						}
					}
					float R0=(nout-nin)*(nout-nin)/((nout+nin)*(nout+nin));
					float R=(float) (R0+(1-R0)*Math.pow((1-cosine), 5));
					return Color3f.Plus(c,Color3f.Multiply(m.getCs(), Color3f.Plus(Color3f.Multiply(this.raycolor(new Ray(h1.intersection,refr), epsilon, Float.MAX_VALUE, s, recursion+1,coefficient*R,h1), R), Color3f.Multiply(this.raycolor(new Ray(h1.intersection,t), epsilon, Float.MAX_VALUE, s, recursion+1,coefficient*(1-R),h1), (1-R))))).clip();
				}
				else {
					k = m.getCs();
					if(m.getNin()!=1)
					{
						k = Color3f.Multiply(k, (float) Math.pow(Vector3f.Inp(h1.normal, r.getDirection()),m.getNin()));
					}
					return Color3f.Plus(c,Color3f.Multiply(k, this.raycolor(new Ray(h1.intersection,refr), epsilon, Float.MAX_VALUE, s, recursion+1,coefficient,h1))).clip();
				}
			}
			return c.clip();
		}
		else {
			return background;
		}

	}


	public boolean refract(Vector3f d, Vector3f normal, float n, float nt, Vector3f t) {
		float sqrt = (float) (1-(n*n)*(1-Math.pow(Vector3f.Inp(d, normal), 2))/(nt*nt));
		if(sqrt < 0) {
			return false;
		}
		else {
			Vector3f tnew = Vector3f.Min(Vector3f.Min(d, normal.multiply(Vector3f.Inp(d, normal))).multiply(n/nt), normal.multiply((float) Math.sqrt(sqrt)));
			t.x=tnew.x;
			t.y=tnew.y;
			t.z=tnew.z;
			return true;
		}

	}
	public void addLight(Light light1) {
		lights.add(light1);
	}
	public ArrayList<Light> getLights() {
		return lights;
	}

	public void setLights(ArrayList<Light> lights) {
		this.lights = lights;
	}

	public float getAmbientIntensity() {
		return ambientIntensity;
	}

	public void setAmbientIntensity(float ambientIntensity) {
		this.ambientIntensity = ambientIntensity;
	}
	public boolean isRelighter() {
		return relighter;
	}
	public void setRelighter(boolean relighter) {
		this.relighter = relighter;
	}
}
